using System;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace Bordecal.ImportsSorter.Properties
{
	/// <summary>
	/// Application settings helper.
	/// </summary>
	internal sealed partial class Settings
	{
		#region Fields

		/// <summary>
		/// Allowed range for the inter-group line count specified by the user.
		/// </summary>
		internal static readonly Range<int> InterGroupLineCountRange = new Range<int>(0, 5);

		/// <summary>
		/// Allowed range for the post-sorting imports block duration specified by the user.
		/// </summary>
		internal static readonly Range<TimeSpan> BlockDisplayDurationRange = new Range<TimeSpan>(TimeSpan.Zero, new TimeSpan(0, 0, 10));

		/// <summary>
		/// Maximum number of sorting groups that may be specified by the user.
		/// </summary>
		internal const int MaxGroupCount = 50;

		/// <summary>
		/// Name of the setting for the inter-group line count.
		/// </summary>
		private const string InterGroupLineCountName = "InterGroupLineCount";

		/// <summary>
		/// Name of the setting for the block display duration.
		/// </summary>
		private const string BlockDisplayDurationName = "BlockDisplayDuration";

		/// <summary>
		/// Name of the setting for the sorting groups collection
		/// </summary>
		private const string SortingGroupsName = "SortingGroups";

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the Settings class.
		/// </summary>
		public Settings()
		{
			this.SettingChanging += new System.Configuration.SettingChangingEventHandler(this.Settings_SettingChanging);
			this.SettingsLoaded += new System.Configuration.SettingsLoadedEventHandler(this.Settings_SettingsLoaded);
		}

		#endregion

		#region Event handlers

		/// <summary>
		/// Validates the settings data when it is loaded from a configuration file.
		/// </summary>
		/// <remarks>
		/// If the loaded settings do not pass validation, the settings are reset
		/// to their default values.
		/// </remarks>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void Settings_SettingsLoaded(object sender, System.Configuration.SettingsLoadedEventArgs e)
		{
			if ((!Settings.InterGroupLineCountValid(this.InterGroupLineCount)) ||
				(!Settings.SortingGroupsValid(this.SortingGroupsArray)) ||
				(!Settings.BlockDisplayDurationValid(this.BlockDisplayDuration)))
			{
				this.Reset();
			}
		}

		/// <summary>
		/// Validates the settings data when they are modified by the user.
		/// </summary>
		/// <remarks>
		/// An exception is thrown in the case of a validation error.
		/// </remarks>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void Settings_SettingChanging(object sender, System.Configuration.SettingChangingEventArgs e)
		{
			try
			{
				switch (e.SettingName)
				{
					case Settings.InterGroupLineCountName:
						if (!Settings.InterGroupLineCountValid((int)e.NewValue))
						{
							e.Cancel = true;
							throw new ArgumentOutOfRangeException(e.SettingName,
								TemplatedResources.OutOfInclusiveRangeValidationMessage(Settings.InterGroupLineCountRange));
						}
						break;

					case Settings.BlockDisplayDurationName:
						if (!Settings.BlockDisplayDurationValid((TimeSpan)e.NewValue))
						{
							e.Cancel = true;
							throw new ArgumentOutOfRangeException(e.SettingName,
								TemplatedResources.OutOfInclusiveRangeValidationMessage(Settings.BlockDisplayDurationRange));
						}
						break;

					case Settings.SortingGroupsName:
						string[] groups = this.StringCollectionToArray((StringCollection)e.NewValue);
						if (!Settings.SortingGroupsLengthValid(groups))
						{
							e.Cancel = true;
							throw new ArgumentOutOfRangeException(e.SettingName,
								TemplatedResources.ListTooLongValidationMessage(Settings.MaxGroupCount));
						}
						else
						{
							string[] duplicates = Settings.SortingGroupsDuplicates(groups);
							if (duplicates.Length > 0)
							{
								e.Cancel = true;
								throw new ArgumentOutOfRangeException(e.SettingName,
									TemplatedResources.DuplicatesInListValidationMessage(duplicates));
							}
						}
						break;
				}
			}
			catch (InvalidCastException)
			{
				e.Cancel = true;
				throw;
			}
		}

		#endregion

		# region Properties

		/// <summary>
		/// Gets or sets the collection of sorting group namespace prefixes 
		/// as a one-dimensional string array.
		/// </summary>
		/// <remarks>
		/// The getter retrieves a copy of the settings data.  Any changes
		/// made to this array by the calling code will not be reflected in
		/// the underlying settings unless the copied array is later
		/// passed to the setter.
		/// </remarks>
		internal string[] SortingGroupsArray
		{
			get
			{
				StringCollection sortingGroupsCollection = this.SortingGroups;
				if (!sortingGroupsCollection.Contains(string.Empty))
					sortingGroupsCollection.Add(string.Empty);

				return this.StringCollectionToArray(sortingGroupsCollection);
			}
			set
			{
				StringCollection sortingGroupsCollection = new StringCollection();
				if (value != null)
					sortingGroupsCollection.AddRange(value);

				this.SortingGroups = sortingGroupsCollection;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Copies a StringCollection to a one-dimensional string array.
		/// </summary>
		/// <param name="source">The StringCollection to be copied to an array.</param>
		/// <returns>
		/// A one-dimensions string array containing the same data as the
		/// source StringCollection.
		/// </returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		private string[] StringCollectionToArray(StringCollection source)
		{
			string[] retVal = null;

			if (source == null)
			{
				retVal = new string[0];
			}
			else
			{
				retVal = new string[source.Count];
				source.CopyTo(retVal, 0);
			}

			return retVal;
		}

		/// <summary>
		/// Validates a string array of sorting group namespace prefixes.
		/// </summary>
		/// <param name="groups">The array to be validated.</param>
		/// <returns>A boolean value indicating whether the data passed validation.</returns>
		internal static bool SortingGroupsValid(string[] groups)
		{
			bool retVal = false;

			if (groups == null)
			{
				retVal = true;
			}
			else
			{
				retVal = Settings.SortingGroupsLengthValid(groups) && (Settings.SortingGroupsDuplicates(groups).Length == 0);
			}

			return retVal;
		}

		/// <summary>
		/// Validates the length of a string array of sorting group namespace prefixes.
		/// </summary>
		/// <param name="groups">The array to be validated.</param>
		/// <returns>A boolean value indicating whether the data passed validation.</returns>
		internal static bool SortingGroupsLengthValid(string[] groups)
		{
			bool retVal = false;

			if (groups == null)
			{
				retVal = true;
			}
			else
			{
				retVal = (groups.Length <= Settings.MaxGroupCount);
			}

			return retVal;
		}

		/// <summary>
		/// Validates that a string array of sorting group namespace prefixes
		/// contains no duplicates.
		/// </summary>
		/// <param name="groups">The array to be validated.</param>
		/// <returns>A boolean value indicating whether the data passed validation.</returns>
		internal static string[] SortingGroupsDuplicates(string[] groups)
		{
			List<string> duplicates = new List<string>();

			if ((groups != null) || (groups.Length > 1))
			{
				for (int i = 0; i < groups.Length; i++)
				{
					string focalValue = groups[i].Trim();
					for (int j = i + 1; j < groups.Length; j++)
					{
						if (groups[j].Trim().Equals(focalValue, StringComparison.OrdinalIgnoreCase))
						{
							duplicates.Add(focalValue);
							break;
						}
					}
				}
			}

			return duplicates.ToArray();
		}

		/// <summary>
		/// Validates the number of lines to be inserted between sorting groups.
		/// </summary>
		/// <param name="testValue">The number of lines to be inserted between sorting groups.</param>
		/// <returns>A boolean value indicating whether the data passed validation.</returns>
		internal static bool InterGroupLineCountValid(int testValue)
		{
			return Settings.InterGroupLineCountRange.IsInRange(testValue);
		}

		/// <summary>
		/// Validates the post-sorting imports block display duration.
		/// </summary>
		/// <param name="testValue">The duration for which to display the imports block after sorting.</param>
		/// <returns>A boolean value indicating whether the data passed validation.</returns>
		internal static bool BlockDisplayDurationValid(TimeSpan testValue)
		{
			return Settings.BlockDisplayDurationRange.IsInRange(testValue);
		}

		#endregion
	}
}